package cn.net.xyan.easy.spring.annotation.processor;

import cn.net.xyan.easy.spring.model.HeadParam;
import cn.net.xyan.easy.spring.model.PathParam;
import cn.net.xyan.easy.spring.model.RestServiceMethod;
import cn.net.xyan.easy.spring.model.RestServiceModule;
import cn.net.xyan.easy.spring.utils.FileObjectUtils;
import cn.net.xyan.easy.spring.utils.TemplateUtils;
import cn.net.xyan.easy.spring.web.annotation.RestServiceConfig;

import org.springframework.web.bind.annotation.*;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.*;
import javax.tools.FileObject;
import java.io.OutputStream;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by xiashenpin on 16/3/6.
 */
@SupportedAnnotationTypes("cn.net.xyan.easy.spring.web.annotation.RestServiceConfig")
public class RestServiceProcessor extends AbstractProcessor {

    ProcessingEnvironment processingEnvironment;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        processingEnvironment = processingEnv;
    }

    public void initModuleClass(RoundEnvironment roundEnv, Map<String,RestServiceModule> context){
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(RequestMapping.class);

        for(Element element:elements){
            if (ElementKind.INTERFACE == element.getKind() || ElementKind.CLASS == element.getKind()){
                TypeElement classElement = (TypeElement) element;
                String name = classElement.getSimpleName().toString();
                String fullTypeName = classElement.getQualifiedName().toString();
                String clientClassName = name+"Async";
                String packageName = null;
                Element pe = classElement.getEnclosingElement();
                if (ElementKind.PACKAGE == pe.getKind()){
                    PackageElement packageElement = (PackageElement) pe;
                    packageName = packageElement.getQualifiedName().toString();
                }

                RestServiceConfig config = classElement.getAnnotation(RestServiceConfig.class);
                if (config!=null){
                    if (config.typeName().length()>0)
                        clientClassName = config.typeName();
                    if (config.packageName().length()>0)
                        packageName = config.packageName();
                }

                RequestMapping requestMapping = classElement.getAnnotation(RequestMapping.class);

                RestServiceModule module = new RestServiceModule();
                module.setPackageName(packageName);
                module.setTypeName(clientClassName);

                if (requestMapping.value()!=null && requestMapping.value().length>0)
                module.setPath(requestMapping.value()[0]);

                context.put(fullTypeName,module);

            }
        }
    }

    public void initMethod(RoundEnvironment roundEnv, Map<String,RestServiceModule> context){
        Set<? extends Element> methodElements = roundEnv.getElementsAnnotatedWith(RequestMapping.class);
        for(Element element:methodElements){
            if (ElementKind.METHOD == element.getKind() ){
                ExecutableElement methodElement = (ExecutableElement) element;

                TypeElement enclosingElement = (TypeElement) methodElement.getEnclosingElement();

                RestController restController = enclosingElement.getAnnotation(RestController.class);

                // 拿到key
                String key = enclosingElement.getQualifiedName().toString();

                if (restController!=null){
                    addMethodElement2Module(key,methodElement,context);
                }else{
                    ResponseBody rb = methodElement.getAnnotation(ResponseBody.class);
                    if (rb!=null){
                        addMethodElement2Module(key,methodElement,context);
                    }
                }

            }
        }
    }

    public void addMethodElement2Module(String key,ExecutableElement executableElement,Map<String,RestServiceModule> context){


        String returnType = executableElement.getReturnType().toString();

        if (returnType.startsWith("org.springframework.data.domain.Page")){
            returnType = returnType.replace("org.springframework.data.domain.Page","cn.net.xyan.easy.gwt.client.model.Page");
        }
        String requestType = null;

        RequestMapping requestMapping = executableElement.getAnnotation(RequestMapping.class);

        List<? extends VariableElement> variableElements =  executableElement.getParameters();

        RestServiceMethod method = new RestServiceMethod();

        for (VariableElement variableElement: variableElements){

            String typeName = variableElement.asType().toString();
            String name = variableElement.getSimpleName().toString();

            if (variableElement.getAnnotation(RequestBody.class)!=null){
                requestType = typeName;
            }else if (variableElement.getAnnotation(PathVariable.class) != null){
                PathVariable pathVariable = variableElement.getAnnotation(PathVariable.class);

                String path = pathVariable.value();
                PathParam pp = new PathParam(typeName,name);
                pp.setPath(path);

                method.getPathParams().add(pp);
            }else if (variableElement.getAnnotation(RequestHeader.class) != null){
                RequestHeader requestHeader = variableElement.getAnnotation(RequestHeader.class);
                String headKey = requestHeader.value();
                HeadParam headParam = new HeadParam(typeName,name);
                headParam.setHeadKey(headKey);
                method.getHeadParams().add(headParam);
            }else if ("org.springframework.data.domain.Pageable".equals(typeName)){
                method.setHasPageable(true);
            }

        }



        if (requestMapping.value()!=null && requestMapping.value().length>0)
            method.setPath(requestMapping.value()[0]);

        method.setRequestTypeName(requestType);
        method.setReturnTypeName(returnType);

        if (requestMapping.produces()!=null && requestMapping.produces().length>0){
            method.setProduceContentType(requestMapping.produces()[0]);
        }

        if (requestMapping.consumes()!=null && requestMapping.consumes().length>0){
            method.setAcceptContentType(requestMapping.consumes()[0]);
        }

        if (requestMapping.method()!=null && requestMapping.method().length>0){
            method.setHttpMethod(requestMapping.method()[0].name());
        }

        method.setMethodName(executableElement.getSimpleName().toString());

        RestServiceModule module = context.get(key);
        module.getMethods().add(method);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        Map<String,RestServiceModule> context = new HashMap<>();

        initModuleClass(roundEnv,context);
        initMethod(roundEnv,context);

        for (RestServiceModule module: context.values()){
            try {
                FileObject fo = FileObjectUtils.fileObject(module,processingEnv);
                OutputStream outputStream = fo.openOutputStream();

                TemplateUtils.render("ClientRestService.ftl", module,outputStream );
                outputStream.flush();
                outputStream.close();


            }catch (Throwable e){

            }
        }


        return true;
    }
}
